#include "pch.h"
#include "DelayedTask.h"
#include "..\Common\SRWLock.h"

CDelayedTask::CDelayedTask()
	: m_pTpTimer(NULL)
{
	AssertInUIThread();
}

CDelayedTask::~CDelayedTask()
{
	AssertInUIThread();
	assert(m_pTpTimer == NULL);
	assert(m_queTasks.empty());
}

bool CDelayedTask::Init()
{
	AssertInUIThread();

	m_pTpTimer = CreateThreadpoolTimer([](PTP_CALLBACK_INSTANCE, PVOID pContext, PTP_TIMER)
	{
		((CDelayedTask *)pContext)->RunTask();
	}, this, NULL);

	return m_pTpTimer != NULL;
}

void CDelayedTask::Exit()
{
	AssertInUIThread();

	if (m_pTpTimer == NULL)
		return;

	// Notify timer to exit
	CExclusiveLock lock(m_SRWLock);
	auto queTasks = std::move(m_queTasks);
	SetThreadpoolTimer(m_pTpTimer, NULL, 0, 0);
	lock.Unlock();

	// Wait for timer to exit
	WaitForThreadpoolTimerCallbacks(m_pTpTimer, TRUE);
	CloseThreadpoolTimer(m_pTpTimer);
	m_pTpTimer = NULL;

	// Sequentially clean up pending tasks
	while (queTasks.size())
	{
		queTasks.pop();
	}
}

bool CDelayedTask::PostTask(UINT nDelayMs, ICallbackPtr&& pTask)
{
	TimedTask task;
	GetSystemTimeAsFileTime(&task.ftDueTime);
	task.nDueTime += (INT64)nDelayMs * 10000;

	UINT nTaskId = pTask->GetId();
	task.pTask = std::move(pTask);

	CExclusiveLock lock(m_SRWLock);
	m_queTasks.emplace(std::move(task));

	// If the new task expires first, reset the timer
	if (m_queTasks.top().pTask->GetId() == nTaskId)
		SetThreadpoolTimer(m_pTpTimer, &((TimedTask&)m_queTasks.top()).ftDueTime, 0, 0);;

	return true;
}

void CDelayedTask::RunTask()
{
	AssertInThreadpool();

	CExclusiveLock lock(m_SRWLock);

	if (m_queTasks.size())
	{
		auto pTask = std::move(((TimedTask&)m_queTasks.top()).pTask);
		m_queTasks.pop();

		// Before running the task, set the next timer
		if (m_queTasks.size())
			SetThreadpoolTimer(m_pTpTimer, &((TimedTask&)m_queTasks.top()).ftDueTime, 0, 0);;

		lock.Unlock();

		pTask->Invoke();
		pTask = nullptr;
	}
}
